package tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.activity.InvalidActivityException;
import org.junit.Before;
import org.junit.Test;
import code.DateFormat;
import code.IPhotoAlbum;
import code.PhotoAlbum;

public class PhotoAlbumTest {

	// -------------------------------------------------------------------------

	/**
	 * Setup by cleaning up the previous run
	 * 
	 * @throws InterruptedException
	 * @throws IOException
	 * @throws InvalidActivityException
	 */
	@Before
	public void setUp() throws InvalidActivityException, IOException, InterruptedException {
		TestHelper.cleanDirectory(TestHelper.RootDir);
	}

	// Basic Tests ------------------------------------------------------------

	@Test(expected = java.security.InvalidParameterException.class)
	public void testPhotoAlbum_CreateFromFileNull() {
		new PhotoAlbum(null);
	}

	/**
	 * Test that the date is populated with correct values when the filename is a valid date based filename
	 * 
	 * @throws InvalidActivityException
	 */
	@Test
	public void testPhotoAlbum_ConstructorSplit() throws InvalidActivityException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMMddUnder, 2000, 12, 3, "_extra");
		PhotoAlbum album = new PhotoAlbum(albums[2]); // create an instance from the third one

		assertEquals("Invalid Year ", 2000, album.getYear());
		assertEquals("Invalid Month", 12, album.getMonth());
		assertEquals("Invalid Day  ", 3, album.getDay());
	}

	/**
	 * Test that the date is populated with correct values when the filename is not a valid date based filename
	 */
	@Test
	public void testPhotoAlbum_ConstructorSplitNotDateDir() throws IOException {
		TestHelper.createFile(TestHelper.RootDir, "test", 1, "file", "description");
		File newFile = new File(TestHelper.RootDir.getPath());

		PhotoAlbum album = new PhotoAlbum(newFile);
		assertEquals("Invalid Year ", -1, album.getYear());
		assertEquals("Invalid Month", -1, album.getMonth());
		assertEquals("Invalid Day  ", -1, album.getDay());
	}

	/**
	 * Test that the date is populated with correct values when the filename is not a valid date based filename (year=1090, ie before
	 * digital cameras)
	 * 
	 * @throws InvalidActivityException
	 */
	@Test
	public void testPhotoAlbum_ConstructorSplitInvalidYear() throws InvalidActivityException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMMddUnder, 1090, 12, 1, "_extra");

		PhotoAlbum album = new PhotoAlbum(albums[0]);
		assertEquals("Invalid Year ", -1, album.getYear());
		assertEquals("Invalid Month", 12, album.getMonth());
		assertEquals("Invalid Day  ", 1, album.getDay());
	}

	/**
	 * Test that the date is populated with correct values when the filename is not a valid date based filename (month=13)
	 * 
	 * @throws InvalidActivityException
	 */
	@Test
	public void testPhotoAlbum_ConstructorSplitInvalidMonth() throws InvalidActivityException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMMddUnder, 2000, 12, 1, "_extra");
		File newName = new File(albums[0].getParent(), "2000_13_1_extra"); // rename the file so it's incorrectly formatted.
		albums[0].renameTo(newName);

		PhotoAlbum album = new PhotoAlbum(newName);
		assertEquals("Invalid Year ", 2000, album.getYear());
		assertEquals("Invalid Month", -1, album.getMonth());
		assertEquals("Invalid Day  ", 1, album.getDay());
	}

	/**
	 * Test that the date is populated with correct values when the filename is not a valid date based filename (month=0)
	 * 
	 * @throws InvalidActivityException
	 */
	@Test
	public void testPhotoAlbum_ConstructorSplitInvalidMonthZero() throws InvalidActivityException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMMddDash.toString(), 2000, 0, 1, "_desc");
		File newName = new File(albums[0].getParent(), "2000-0-1-desc"); // rename the file so it's incorrectly formatted.
		albums[0].renameTo(newName);

		PhotoAlbum album = new PhotoAlbum(newName);

		assertEquals("Invalid Year ", 2000, album.getYear());
		assertEquals("Invalid Month", -1, album.getMonth());
		assertEquals("Invalid Day  ", 1, album.getDay());
	}

	/**
	 * Test that the date is populated with correct values when the filename is not a valid date based filename (day)
	 * 
	 * @throws InvalidActivityException
	 */
	@Test
	public void testPhotoAlbum_ConstructorSplitInvalidDay() throws InvalidActivityException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMMddUnder, 2000, 1, 1, " - Extra");
		File newName = new File(albums[0].getParent(), "2010_1_32 - Extra"); // rename the file so it has an invalid day
		albums[0].renameTo(newName);

		PhotoAlbum album = new PhotoAlbum(newName);

		assertEquals("Invalid Year ", 2010, album.getYear());
		assertEquals("Invalid Month", 1, album.getMonth());
		assertEquals("Invalid Day  ", -1, album.getDay());
	}

	/**
	 * Test that the date can be successfully obtained from the fileName when the filename does not have a description
	 * 
	 * @throws InvalidActivityException
	 */
	@Test
	public void testPhotoAlbum_GetDateFromNameNoDesc() throws InvalidActivityException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMMddDash.toString(), 2009, 5, 1, "");

		IPhotoAlbum album = new PhotoAlbum(albums[0]);

		assertEquals("Invalid Year ", 2009, album.getYear());
		assertEquals("Invalid Month", 5, album.getMonth());
		assertEquals("Invalid Day  ", 1, album.getDay());
		assertEquals("Invalid Extra", "", album.getExtra());
	}

	/**
	 * Test that the date can be successfully obtained from the fileName when the filename was made up with a dash delimiter
	 * 
	 * @throws InvalidActivityException
	 */
	@Test
	public void testPhotoAlbum_GetDateFromNameDash() throws InvalidActivityException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMMddDash.toString(), 2009, 5, 1, "-extra");
		assertEquals(albums.length, 1);

		IPhotoAlbum album = new PhotoAlbum(albums[0]);

		assertEquals("Invalid Year ", 2009, album.getYear());
		assertEquals("Invalid Month", 5, album.getMonth());
		assertEquals("Invalid Day  ", 1, album.getDay());
		assertEquals("Invalid Extra", "extra", album.getExtra());
	}

	/**
	 * Test that the date can be successfully obtained from the fileName when the filename was made up with an underscore delimiter
	 * 
	 * @throws InvalidActivityException
	 */
	@Test
	public void testPhotoAlbum_GetExtraUnder() throws InvalidActivityException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMMddUnder, 2009, 5, 1, "_extra");
		assertEquals(albums.length, 1);

		IPhotoAlbum album = new PhotoAlbum(albums[0]);

		assertEquals("Invalid Year ", 2009, album.getYear());
		assertEquals("Invalid Month", 5, album.getMonth());
		assertEquals("Invalid Day  ", 1, album.getDay());
		assertEquals("Invalid Extra", "extra", album.getExtra());
	}

	/**
	 * Test that the date can be successfully obtained from the fileName when the filename has extra with no space or delimeter
	 * 
	 * @throws InvalidActivityException
	 */
	@Test
	public void testPhotoAlbum_GetExtraNoDelimeterBeforeExtra() throws InvalidActivityException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMMddUnder, 2009, 5, 1, "extra");
		IPhotoAlbum album = new PhotoAlbum(albums[0]);
		assertEquals("Invalid Year ", 2009, album.getYear());
		assertEquals("Invalid Month", 5, album.getMonth());
		assertEquals("Invalid Day  ", 1, album.getDay());
		assertEquals("Invalid Extra", "extra", album.getExtra());
	}

	/**
	 * Test that the date can be successfully obtained from the fileName when the filename has extra with spaces before it.
	 * 
	 * @throws InvalidActivityException
	 */
	@Test
	public void testPhotoAlbum_GetExtraSpacesBeforeExtra() throws InvalidActivityException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMMddUnder, 2009, 5, 1, "  extra");
		IPhotoAlbum album = new PhotoAlbum(albums[0]);
		assertEquals("Invalid Year ", 2009, album.getYear());
		assertEquals("Invalid Month", 5, album.getMonth());
		assertEquals("Invalid Day  ", 1, album.getDay());
		assertEquals("Invalid Extra", "extra", album.getExtra());
	}

	/**
	 * Test that the date can be successfully obtained from the fileName when the filename was made of just year and month and without
	 * delimiter
	 */
	@Test
	public void testPhotoAlbum_GetDateFromName_yymmDatesWithoutParameters() throws InvalidActivityException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMM, 2009, 5, 3);
		assertEquals(albums.length, 3);
		IPhotoAlbum album0 = new PhotoAlbum(albums[0]);

		assertEquals(2009, album0.getYear());
		assertEquals(5, album0.getMonth());
		assertEquals(0, album0.getDay());

		PhotoAlbum album1 = new PhotoAlbum(albums[1]);
		assertEquals(2009, album1.getYear());
		assertEquals(6, album1.getMonth());
		assertEquals(0, album1.getDay());

		PhotoAlbum album2 = new PhotoAlbum(albums[2]);
		assertEquals(2009, album2.getYear());
		assertEquals(7, album2.getMonth());
		assertEquals(0, album2.getDay());
	}

	@Test
	public void testPhotoAlbum_GetAlternateFormats() throws InvalidActivityException {
		TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMM, 2009, 5, 1);

		IPhotoAlbum album = new PhotoAlbum(TestHelper.RootDir);
		ArrayList<DateFormat> altFormats = album.getAlternateFormats();
		assertEquals(5, altFormats.size());
	}

	@Test
	public void testPhotoAlbum_ParentRoot() throws InvalidActivityException {
		TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMM, 2009, 5, 1);

		PhotoAlbum rootAlbum = new PhotoAlbum(TestHelper.RootDir);
		assertNull(rootAlbum.getParent());
	}

	@Test
	public void testPhotoAlbum_AlbumCount() throws InvalidActivityException {
		// create an album structure where we have 2 albums in the root, and in each of them, there are 2 and 3 sub albums respectively
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMM, 2009, 5, 2);
		TestHelper.createDateBasedPhotoDirs(albums[0], DateFormat.yyyyMM, 2010, 1, 2);
		TestHelper.createDateBasedPhotoDirs(albums[1], DateFormat.yyyyMM, 2011, 1, 3);

		IPhotoAlbum rootAlbum = new PhotoAlbum(TestHelper.RootDir);
		assertNull(rootAlbum.getParent());
		assertEquals(2, TestHelper.RootDir.listFiles().length);
		assertEquals(2, rootAlbum.albumsCount());
		assertEquals(7, rootAlbum.albumsCountRecursive());
		assertEquals(2, albums[0].listFiles().length);
		assertEquals(2, rootAlbum.albumAt(0).albumsCount());
		assertEquals(3, rootAlbum.albumAt(1).albumsCount());
	}

	@Test
	public void testPhotoAlbum_PhotoCount() throws IOException {
		// create an album structure where we have 2 albums in the root, and in each of them, there are 1 and 2 sub albums respectively
		// in these deepest albums, create 1 photo for each album, so there should be 3 photos
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMM, 2009, 5, 2);
		File[] sub1 = TestHelper.createDateBasedPhotoDirs(albums[0], DateFormat.yyyyMM, 2010, 1, 1);
		File[] sub2 = TestHelper.createDateBasedPhotoDirs(albums[1], DateFormat.yyyyMM, 2011, 1, 2);

		TestHelper.createPhotos(sub1[0], "jpg", 1);
		TestHelper.createPhotos(sub2[0], "jpg", 1);
		TestHelper.createPhotos(sub2[1], "jpg", 1);

		IPhotoAlbum rootAlbum = new PhotoAlbum(TestHelper.RootDir);
		assertNull(rootAlbum.getParent());
		assertEquals(0, rootAlbum.photosCount());
		assertEquals(3, rootAlbum.photosCountRecursive());
		assertEquals(0, rootAlbum.albumAt(0).photosCount());
		assertEquals(1, rootAlbum.albumAt(0).albumAt(0).photosCount());
		assertEquals(1, rootAlbum.albumAt(1).albumAt(0).photosCount());
		assertEquals(1, rootAlbum.albumAt(1).albumAt(1).photosCount());
	}

	@Test
	public void testPhotoAlbum_ParentValid() throws InvalidActivityException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMM, 2009, 5, 1);

		IPhotoAlbum album = new PhotoAlbum(albums[0], new PhotoAlbum(TestHelper.RootDir));
		IPhotoAlbum parent = album.getParent();

		assertNotNull(parent); // the parent of the album is the root.
		assertNull(parent.getParent()); // there is no parent for the root

		String expected = parent.getPath();
		String actual = TestHelper.RootDir.getPath();

		assertEquals(actual, expected); // the parent of this album is the root.
	}

	@Test
	public void testPhotoAlbum_CollapseSubDirectoriesNoConflicts() throws Exception {
		int numAlbums = 10;
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMM, 2009, 5, numAlbums);

		int c1 = 5;
		int c2 = 3;
		int cTotal = c1 + c2;

		for (int i = 1; i <= c1; ++i)
			assertNotNull(TestHelper.createFile(albums[0], "2009_05_", i, "album1", "jpg"));

		for (int i = 1; i <= c2; ++i)
			assertNotNull(TestHelper.createFile(albums[1], "2009_06_", i, "album2", "jpg"));

		for (int i = 0; i < numAlbums; ++i)
			assertEquals(true, albums[i].exists());

		// test that we've setup as we expect
		assertEquals("Album1 size before collapse should be " + c1, c1, albums[0].listFiles().length);
		assertEquals("Album2 size before collapse should be " + c2, c2, albums[1].listFiles().length);

		IPhotoAlbum rootAlbum = new PhotoAlbum(TestHelper.RootDir);
		assertEquals("Album size before collapse", rootAlbum.photosCountRecursive(), cTotal);

		rootAlbum.collapseAlbums();

		assertEquals(false, albums[0].exists());
		assertEquals(false, albums[1].exists());

		for (int i = 2; i < numAlbums; ++i)
			assertEquals("These albums should not exist after a collapse", false, albums[i].exists());

		assertEquals("Number of pics after collapse should still be " + cTotal, cTotal, rootAlbum.photosCountRecursive());
		assertEquals("Number of albums after collapse should be 0", 0, rootAlbum.albumsCountRecursive());
	}

	@Test
	public void testPhotoAlbum_CollapseSubDirectoriesWithConflicts() throws Exception {
		int c1 = 2;
		int c2 = 3;
		int cTotal = c1 + c2;

		// create 2 directories under root. In these sub-directories, add c1 and c2 sub directories. In each of these, add 2 photos
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMM, 2009, 5, 2);
		File[] sub1 = TestHelper.createDateBasedPhotoDirs(albums[0], DateFormat.yyyyMM, 2010, 1, c1);
		File[] sub2 = TestHelper.createDateBasedPhotoDirs(albums[1], DateFormat.yyyyMM, 2011, 1, c2);

		for (int i = 0; i < c1; ++i)
			TestHelper.createPhotos(sub1[i], "jpg", 2);

		for (int i = 0; i < c2; ++i)
			TestHelper.createPhotos(sub2[i], "jpg", 2);

		IPhotoAlbum rootAlbum = new PhotoAlbum(TestHelper.RootDir);
		assertEquals("Album size before collapse", 2, rootAlbum.albumsCount());
		assertEquals("Album size before collapse", cTotal + 2, rootAlbum.albumsCountRecursive());
		assertEquals("Pic count before collapse", cTotal * 2, rootAlbum.photosCountRecursive()); // 2 pics in each folder

		rootAlbum.collapseAlbums();

		// there were conflicts at multiple levels here. Lets go through each level...
		assertEquals(true, albums[0].exists());
		assertEquals(true, albums[1].exists());
		assertEquals(2, rootAlbum.photosCount()); // 2 photos should have been copied to the root from the first directory
		assertEquals(cTotal * 2, rootAlbum.photosCountRecursive()); // same as before we started
		assertEquals(rootAlbum.albumAt(0).photosCount(), 0); // nothing here (potential for improvement in the algorithm).
		assertEquals(rootAlbum.albumAt(1).photosCount(), 2); // assertEquals(rootAlbum.albumAt(0), 0);

		assertEquals(false, sub1[0].exists()); // the photos got collapsed eventually into root, and the album was consequently deleted
		assertEquals(true, sub1[1].exists()); // after the first album, there were conflicts, so this remains (there is potential here for
												// the move to be done again, but that is getting quite tedious. Running the algorithm again
												// will achieve the same result.

		assertEquals(false, sub2[0].exists()); // the photos got collapsed into Album2, and the album was consequently deleted
		assertEquals(true, sub2[1].exists()); // there were conflicts from the first album in this sub album, so it remains.
		assertEquals(true, sub2[1].exists()); // there were conflicts from the first album in this sub album, so it remains.
	}

	@Test
	public void testPhotoAlbum_RenamePhotosInAlbums() throws IOException {
		File[] albums = TestHelper.createDateBasedPhotoDirs(TestHelper.RootDir, DateFormat.yyyyMMddUnder, 2009, 5, 1);

		//@formatter:off
		assertNotNull(TestHelper.createFile(albums[0], "Image ", 0, " - extra",	"jpg"));
		assertNotNull(TestHelper.createFile(albums[0], "Image ", 1, "- extra", 	"jpg"));
		assertNotNull(TestHelper.createFile(albums[0], "Image ", 2, "  extra", 	"jpg"));
		assertNotNull(TestHelper.createFile(albums[0], "Image ", 3, "extra", 	"jpg"));
		assertNotNull(TestHelper.createFile(albums[0], "Image ", 4, "_extra", 	"jpg"));
		assertNotNull(TestHelper.createFile(albums[0], "Image ", 5, "", 		"jpg"));
		//@formatter:on

		IPhotoAlbum rootAlbum = new PhotoAlbum(TestHelper.RootDir);
		IPhotoAlbum album = rootAlbum.albumAt(0);

		//@formatter:off
		assertEquals("Image 0  - extra.jpg", album.photoAt(0).getNameNew());
		assertEquals("Image 1 - extra.jpg",  album.photoAt(1).getNameNew());
		assertEquals("Image 2   extra.jpg",  album.photoAt(2).getNameNew());
		assertEquals("Image 3 extra.jpg", 	 album.photoAt(3).getNameNew());
		assertEquals("Image 4 _extra.jpg", 	 album.photoAt(4).getNameNew());
	//	assertEquals("Image 5.jpg", 		 album.photoAt(5).getNameNew());
		// @formatter:on

		// do the rename
		rootAlbum.renamePhotosInAlbums();

		// now test the photos in each album after the rename
		IPhotoAlbum albumAfter = rootAlbum.albumAt(0);
		String testAfter = albumAfter.photoAt(0).getNameNew();

		assertEquals("2009_05_02 0  - extra.jpg", albumAfter.photoAt(0).getNameNew());
		assertEquals("2009_05_02 1 - extra.jpg", albumAfter.photoAt(1).getNameNew());
		assertEquals("2009_05_02 2   extra.jpg", albumAfter.photoAt(2).getNameNew());
		assertEquals("2009_05_02 3 extra.jpg", albumAfter.photoAt(3).getNameNew());
		assertEquals("2009_05_02 4 _extra.jpg", albumAfter.photoAt(4).getNameNew());
		assertEquals("2009_05_02 5 _extra.jpg", albumAfter.photoAt(5).getNameNew());
	}
}
